All files / core/view/filter IndexedFilter.ts

93.88% Statements 46/49
86.67% Branches 26/30
100% Functions 10/10
93.75% Lines 45/48
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167                                12x 12x 12x 12x                             12x 610x   12x               177x       177x   177x           47x         47x       130x 70x 7x 7x                 63x 47x       16x         130x       130x             12x         1688x 708x 549x 735x 21x           708x 474x 1112x 714x 714x 10x         398x             1688x           12x 6x     6x             12x 1185x           12x 261x           12x 1392x   12x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { assert } from '@firebase/util';
import { Change } from '../Change';
import { ChildrenNode } from '../../snap/ChildrenNode';
import { PRIORITY_INDEX } from '../../snap/indexes/PriorityIndex';
import { NodeFilter } from './NodeFilter';
import { Index } from '../../snap/indexes/Index';
import { Path } from '../../util/Path';
import { CompleteChildSource } from '../CompleteChildSource';
import { ChildChangeAccumulator } from '../ChildChangeAccumulator';
import { Node } from '../../snap/Node';
 
/**
 * Doesn't really filter nodes but applies an index to the node and keeps track of any changes
 *
 * @constructor
 * @implements {NodeFilter}
 * @param {!Index} index
 */
export class IndexedFilter implements NodeFilter {
  constructor(private readonly index_: Index) {}
 
  updateChild(
    snap: Node,
    key: string,
    newChild: Node,
    affectedPath: Path,
    source: CompleteChildSource,
    optChangeAccumulator: ChildChangeAccumulator | null
  ): Node {
    assert(
      snap.isIndexed(this.index_),
      'A node must be indexed if only a child is updated'
    );
    const oldChild = snap.getImmediateChild(key);
    // Check if anything actually changed.
    if (
      oldChild.getChild(affectedPath).equals(newChild.getChild(affectedPath))
    ) {
      // There's an edge case where a child can enter or leave the view because affectedPath was set to null.
      // In this case, affectedPath will appear null in both the old and new snapshots.  So we need
      // to avoid treating these cases as "nothing changed."
      Eif (oldChild.isEmpty() == newChild.isEmpty()) {
        // Nothing changed.
 
        // This assert should be valid, but it's expensive (can dominate perf testing) so don't actually do it.
        //assert(oldChild.equals(newChild), 'Old and new snapshots should be equal.');
        return snap;
      }
    }
 
    if (optChangeAccumulator != null) {
      if (newChild.isEmpty()) {
        Eif (snap.hasChild(key)) {
          optChangeAccumulator.trackChildChange(
            Change.childRemovedChange(key, oldChild)
          );
        } else {
          assert(
            snap.isLeafNode(),
            'A child remove without an old child only makes sense on a leaf node'
          );
        }
      } else if (oldChild.isEmpty()) {
        optChangeAccumulator.trackChildChange(
          Change.childAddedChange(key, newChild)
        );
      } else {
        optChangeAccumulator.trackChildChange(
          Change.childChangedChange(key, newChild, oldChild)
        );
      }
    }
    Iif (snap.isLeafNode() && newChild.isEmpty()) {
      return snap;
    } else {
      // Make sure the node is indexed
      return snap.updateImmediateChild(key, newChild).withIndex(this.index_);
    }
  }
 
  /**
   * @inheritDoc
   */
  updateFullNode(
    oldSnap: Node,
    newSnap: Node,
    optChangeAccumulator: ChildChangeAccumulator | null
  ): Node {
    if (optChangeAccumulator != null) {
      if (!oldSnap.isLeafNode()) {
        oldSnap.forEachChild(PRIORITY_INDEX, function(key, childNode) {
          if (!newSnap.hasChild(key)) {
            optChangeAccumulator.trackChildChange(
              Change.childRemovedChange(key, childNode)
            );
          }
        });
      }
      if (!newSnap.isLeafNode()) {
        newSnap.forEachChild(PRIORITY_INDEX, function(key, childNode) {
          if (oldSnap.hasChild(key)) {
            const oldChild = oldSnap.getImmediateChild(key);
            if (!oldChild.equals(childNode)) {
              optChangeAccumulator.trackChildChange(
                Change.childChangedChange(key, childNode, oldChild)
              );
            }
          } else {
            optChangeAccumulator.trackChildChange(
              Change.childAddedChange(key, childNode)
            );
          }
        });
      }
    }
    return newSnap.withIndex(this.index_);
  }
 
  /**
   * @inheritDoc
   */
  updatePriority(oldSnap: Node, newPriority: Node): Node {
    Iif (oldSnap.isEmpty()) {
      return ChildrenNode.EMPTY_NODE;
    } else {
      return oldSnap.updatePriority(newPriority);
    }
  }
 
  /**
   * @inheritDoc
   */
  filtersNodes(): boolean {
    return false;
  }
 
  /**
   * @inheritDoc
   */
  getIndexedFilter(): IndexedFilter {
    return this;
  }
 
  /**
   * @inheritDoc
   */
  getIndex(): Index {
    return this.index_;
  }
}